﻿#region Copyright

// Majesty of Omega - The next evolution of macro management space strategy
// 
// Copyright 2007-2009 André Claaßen
// 
// This file is protected by the terms and conditions of the
// Microsoft Community License (Ms-CL 1.1), a copy of which should
// have been distributed along with this software. If not,
// you may find the license information at the following URL:
// 
// http://www.microsoft.com/resources/sharedsource/licensingbasics/communitylicense.mspx

#endregion

#region Imports

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using DesignByContract;
using Majesty_of_Omega.Config;
using Majesty_of_Omega.GUI.Pages.Game.Research;
using Majesty_of_Omega.GUI.WPFUtil;
using Majesty_of_Omega.Model.Game;
using Majesty_of_Omega.Model.Report;
using Majesty_of_Omega.Model.Research;

#endregion

namespace Majesty_of_Omega.GUI.Controller
{
    /// <summary>
    /// The Controller for the whole research screen and the interaction logic of the ResearchPage.Xaml
    /// </summary>
    public class ResearchPageController : InGameController, INotifyPropertyChanged
    {
        /// <summary>
        /// this field is not null, when the ResearchPage is invoked from an entry 
        /// of the situation report
        /// </summary>
        private readonly SitReportEntry sitRepEntry;

        /// <summary>
        /// List of the available applied technologies. The list will be used at the 
        /// applied research tab
        /// </summary>
        private MooObservableCollection<AppliedResearchItem> availableAppliedTechnlogiesList;

        /// <summary>
        /// The applied technology projects will be managed in a Observable collection,
        /// because we must notify the WPF-Controls, when objects in the collections
        /// will be changed
        /// </summary>
        private MooObservableCollection<AppliedResearchItem> currentAppliedResearchList;

        /// <summary>
        /// The currently selected applied research
        /// </summary>
        private AppliedResearchItem selectedAppliedResearch;

        /// <summary>
        /// The currently selected Tech School in the user interface
        /// </summary>
        private TechInfoItem selectedTechSchoolInfoItem;

        /// <summary>
        /// The currently selected Tech Field in the user interface
        /// </summary>
        private TechInfoItem selectedTechFieldInfoItem;

        /// <summary>
        /// Initializes a new instance of the <see cref="ResearchPageController"/> class.
        /// </summary>
        /// <remarks>
        /// This constructor serves for testing purposes.
        /// </remarks>
        public ResearchPageController()
        {
            GenerateTestCurrentAppliedTechnologies(saveGame);
            GenerateTestAvailableAppliedTechnologies(saveGame);
            InitController();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ResearchPageController"/> class.
        /// </summary>
        /// <param name="game">The game.</param>
        /// <param name="sitRepEntry">is not null, when this controller is a navigation target from a situation report</param>
        public ResearchPageController(SaveGame game, SitReportEntry sitRepEntry)
            : base(game)
        {
            this.sitRepEntry = sitRepEntry;
            InitController();
        }

        /// <summary>
        /// Gets the research manager from the human player
        /// </summary>
        /// <value>The research manager.</value>
        public ResearchManager ResearchManager
        {
            get
            {
                Require.IsNotNull(saveGame);
                Require.IsNotNull(saveGame.HumanPlayer);
                return saveGame.HumanPlayer.ResearchManager;
            }
        }

        /// <summary>
        /// Gets or sets the selected tech school.
        /// </summary>
        /// <remarks>
        /// This property is databound with the <see cref="ResearchPage.TechSchoolListView"/> control
        /// </remarks>
        /// <value>The selected tech school.</value>
        public TechInfoItem SelectedTechSchool
        {
            get { return selectedTechSchoolInfoItem; }
            set
            {
                selectedTechSchoolInfoItem = value;
                InvokePropertyChanged("SelectedTechSchool");
                InvokePropertyChanged("TechFieldResearchList");
            }
        }

        /// <summary>
        /// Gets or sets the selected tech field.
        /// </summary>
        /// <remarks>
        /// This property is databound with the <see cref="ResearchPage.TechFieldListView"/> control
        /// </remarks>
        /// <value>The selected tech school.</value>
        public TechInfoItem SelectedTechField
        {
            get
            {
                return selectedTechFieldInfoItem;
            }
            set
            {
                selectedTechFieldInfoItem = value;
            }
        }

        /// <summary>
        /// Gets or sets the selected applied research.
        /// </summary>
        /// <value>The selected applied research.</value>
        public AppliedResearchItem SelectedAppliedResearch
        {
            get { return selectedAppliedResearch; }
            set
            {
                selectedAppliedResearch = value;
                InvokePropertyChanged("SelectedAppliedResearch");
                InvokePropertyChanged("SelectedAppliedResearchFunding");
            }
        }

        /// <summary>
        /// Gets or sets the selected applied research funding.
        /// </summary>
        /// <remarks>
        /// Changes to this property will be notified to wpf
        /// </remarks>
        /// <value>The selected applied research funding.</value>
        public FundingLevel SelectedAppliedResearchFunding
        {
            get
            {
                if (selectedAppliedResearch == null)
                    return FundingLevel.Cancel;

                return selectedAppliedResearch.BudgetFundingLevel;
            }
            set
            {
                selectedAppliedResearch.BudgetFundingLevel = value;
                InvokePropertyChanged("SelectedAppliedResearchFunding");
                InvokePropertyChanged("FundamentalResearch");
                InvokePropertyChanged("AppliedResearch");

                // This functinon call signals wpf, that the contents of the applied technologies
                // were changed.
                currentAppliedResearchList.SignalCollectionChanged();
            }
        }

        /// <summary>
        /// Gets the funding levels infos for the combobox
        /// </summary>
        /// <value>The funding levels.</value>
        public IEnumerable<ResearchFundingLevelInfo> FundingLevelInfoLevels
        {
            get { return SpreadSheetManager.ResearchConfiguration.FundingLevels; }
        }

        /// <summary>
        /// Gets the tech field research list.
        /// </summary>
        /// <value>The tech field research list.</value>
        public TechInfoCollection TechFieldResearchList
        {
            get
            {
                TechInfoCollection techInfoCollection = new TechInfoCollection();
                TechField techFieldHighlighted = GetHighlightedTechFieldFromSitRep();

                var researchList = ResearchManager.GetTechFieldResearchForTechSchool(
                    ((TechSchoolResearch)selectedTechSchoolInfoItem.ResearchItem).TechSchool);

                foreach (var research in researchList)
                {
                    techInfoCollection.Add(
                        new TechInfoItem(
                            techInfoCollection,
                            research,
                            research.TechField.Name,
                            ShouldTechFieldHighlighted(research, techFieldHighlighted))
                        );
                }
                return techInfoCollection;
            }
        }

        /// <summary>
        /// Gets the tech school research list.
        /// </summary>
        /// <value>The tech school research list.</value>
        public TechInfoCollection TechSchoolResearchList
        {
            get
            {
                TechField highlightableTechField = GetHighlightedTechFieldFromSitRep();
                TechInfoCollection techInfoCollection = new TechInfoCollection();
                foreach (var research in ResearchManager.TechSchoolResearchs)
                {
                    techInfoCollection.Add(
                        new TechInfoItem(
                            techInfoCollection,
                            research,
                            research.TechSchool.Name,
                            ShouldTechSchoolHighlighted(research, highlightableTechField))
                        // true, if the tech school is the desired tech school for flashing
                        );
                }
                return techInfoCollection;
            }
        }

        /// <summary>
        /// Gets the next upcoming technologies.
        /// </summary>
        /// <value>The upcoming technologies.</value>
        public IEnumerable<TechApplication> UpcomingTechnologies
        {
            get { return ResearchManager.GetNextUpcomingTechnologies(3); }
        }

        /// <summary>
        /// Gets the list of current applied technology projects
        /// </summary>
        /// <value>The applied technologie list.</value>
        public ObservableCollection<AppliedResearchItem> CurrentAppliedTechnologiesList
        {
            get { return currentAppliedResearchList; }
        }

        /// <summary>
        /// Gets the list of available applied technology projects
        /// </summary>
        /// <value>The applied technologie list.</value>
        public ObservableCollection<AppliedResearchItem> AvailableAppliedTechnologiesList
        {
            get { return availableAppliedTechnlogiesList; }
        }

        /// <summary>
        /// Gets the applied research in percent or points
        /// </summary>
        /// <value>The applied research.</value>
        public int AppliedResearch
        {
            get { return ResearchManager.SumOfAppliedResearchPoints; }
        }

        /// <summary>
        /// Gets the fundamental research in percent or points
        /// </summary>
        /// <value>The applied research.</value>
        public int FundamentalResearch
        {
            get { return ResearchManager.SumOfFundamentalResearchPoints; }
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Notify wpf, that a databound property of the <see cref="ResearchPageController" /> has changed
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        /// <summary>
        /// Generates the test available applied technologies.
        /// </summary>
        /// <param name="game">The game.</param>
        private static void GenerateTestAvailableAppliedTechnologies(SaveGame game)
        {
            var testCurrentResearchTechnologies = new[] { "FtrWpn01", "LasrCann", "MisslArm", "Hull01" };

            foreach (var testTechnology in testCurrentResearchTechnologies)
            {
                game.HumanPlayer.ResearchManager.TechCurrentAppliedResearchProjects.Add(
                    new AppliedResearchItem(testTechnology));
            }
        }

        /// <summary>
        /// Generates the test applied technologies for editing purposes in blend
        /// </summary>
        /// <param name="game">The game.</param>
        private static void GenerateTestCurrentAppliedTechnologies(SaveGame game)
        {
            var testCurrentResearchTechnologies = new[] { "PhaBeam", "Mass_10", "FtrWpn06" };

            foreach (var testTechnology in testCurrentResearchTechnologies)
            {
                game.HumanPlayer.ResearchManager.TechAvailableAppliedResearchs.Add(
                    new AppliedResearchItem(testTechnology));
            }
        }

        /// <summary>
        /// Initialize this controller with the current game state
        /// </summary>
        private void InitController()
        {
            TechField highlightedTechField = GetHighlightedTechFieldFromSitRep();

            if (highlightedTechField == null)
            {
                this.selectedTechSchoolInfoItem = TechSchoolResearchList[0];
                this.selectedTechFieldInfoItem = TechFieldResearchList[0];
            }
            else
            {
                selectedTechSchoolInfoItem =
                    TechSchoolResearchList.First(
                        ri => ri.ResearchItem.ResearchItemTypeID == highlightedTechField.TechSchool.Type_ID);
                selectedTechFieldInfoItem =
                    TechFieldResearchList.First(ri => ri.ResearchItem.ResearchItemTypeID == highlightedTechField.Type_ID);
            }

            if (ResearchManager.TechCurrentAppliedResearchProjects.Count > 0)
            {
                selectedAppliedResearch = ResearchManager.TechCurrentAppliedResearchProjects[0];
            }
            currentAppliedResearchList =
                new MooObservableCollection<AppliedResearchItem>(ResearchManager.TechCurrentAppliedResearchProjects);

            availableAppliedTechnlogiesList =
                new MooObservableCollection<AppliedResearchItem>(ResearchManager.TechAvailableAppliedResearchs);
        }

        /// <summary>
        /// This function determines, if a tech field should be highlighted in dependence of the 
        /// sitrep item
        /// </summary>
        /// <returns>returns the to highlightend tech field otherwise null</returns>
        public TechField GetHighlightedTechFieldFromSitRep()
        {
            if (this.sitRepEntry == null) return null;
            if (!(this.sitRepEntry.TargetHyperlinkObject is TechField)) return null;
            return (TechField)this.sitRepEntry.TargetHyperlinkObject;
        }

        /// <summary>
        /// Returns true, if the given techfieldresearch should be highlighted in the wpf ´page
        /// </summary>
        /// <param name="research">The research.</param>
        /// <param name="highlighted">The highlighted.</param>
        /// <returns></returns>
        private static bool ShouldTechFieldHighlighted(TechFieldResearch research, TechField highlighted)
        {
            if (highlighted == null) return false;
            return research.TechField.Name == highlighted.Name;
        }

        /// <summary>
        /// Shoulds the tech school highlighted.
        /// </summary>
        /// <param name="research">The research.</param>
        /// <param name="techField">Name of the highlight tech school.</param>
        /// <returns></returns>
        private static bool ShouldTechSchoolHighlighted(TechSchoolResearch research, TechField techField)
        {
            if (techField == null) return false;
            return research.TechSchool.Name == techField.TechSchool.Name;
        }

        /// <summary>
        /// Notify wpf, that a databound property of the <see cref="ResearchPageController" /> has changed
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        internal void InvokePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Adds the selected available research to the list of the current projects
        /// </summary>
        public void AddSelectedAvailableResearch(AppliedResearchItem selectedAvailableAppliedResearchItem)
        {
            if (!availableAppliedTechnlogiesList.Contains(selectedAvailableAppliedResearchItem)) return;

            ResearchManager.AddResearchToCurrentAppliedResearchProjects(selectedAvailableAppliedResearchItem);
            currentAppliedResearchList.Refresh();
            availableAppliedTechnlogiesList.Refresh();
            SelectedAppliedResearch = selectedAvailableAppliedResearchItem;
            InvokePropertyChanged("FundamentalResearch");
            InvokePropertyChanged("AppliedResearch");
        }

        #region Nested type: TechInfoCollection

        /// <summary>
        /// The TechInfoCollection is a container for a group of interdependent Research Items,
        /// like the Technology Schools or the Technology Fields for a Technology School.
        /// The TechInfoCollection ensures, that the sum of all research percentage values
        /// of the various tech schools or tech fields is always 100 percent.
        /// </summary>
        public class TechInfoCollection : List<TechInfoItem>
        {
            /// <summary>
            /// index of the next research item, which gets a remainder percentage. 
            /// <seealso cref="GetNextRemainderItem"/>
            /// </summary>
            private int iRemainingPointsIndex;

            /// <summary>
            /// Calculates the sum of all research points
            /// </summary>
            /// <value>The sum of research points.</value>
            public int SumOfResearchPoints
            {
                get { return this.Sum(ti => ti.Percentage); }
            }

            /// <summary>
            /// This function computes the next ID for a <see cref="TechInfoItem"/>, 
            /// where you could add a remaning research point.
            /// </summary>
            /// <returns>ID of a <see cref="TechInfoItem"/> </returns>
            private TechInfoItem GetNextRemainderItem()
            {
                TechInfoItem item = this[iRemainingPointsIndex++];
                iRemainingPointsIndex = iRemainingPointsIndex % Count;
                return item;
            }

            /// <summary>
            /// Every time, when the user change the research percentage values of a TechSchool or a TechField
            /// with a slider control, this function will be invoked. The function calculates the difference
            /// between 100 percent and the sum of all percentage values. The difference will be spread over
            /// all unchanged research items. 
            /// </summary>
            /// <param name="changedItem">The changed item.</param>
            internal void OnTechInfoItemChanged(TechInfoItem changedItem)
            {
                while (SumOfResearchPoints != 100)
                {
                    int fragment = (100 - SumOfResearchPoints) / (Count - 1);
                    if (fragment != 0)
                    {
                        foreach (var techInfoItem in this)
                        {
                            if (techInfoItem != changedItem)
                            {
                                techInfoItem.PercentageInternal += fragment;
                            }
                        }
                    }

                    int remainingPoints = 100 - SumOfResearchPoints;
                    int offset = remainingPoints > 0 ? 1 : -1;
                    int count = Math.Abs(remainingPoints);
                    while (count > 0)
                    {
                        TechInfoItem remainderItem = GetNextRemainderItem();
                        if (remainderItem == changedItem) continue;
                        if (remainderItem.PercentageInternal + offset < 0) continue;
                        if (remainderItem.PercentageInternal + offset > 100) continue;
                        remainderItem.PercentageInternal += offset;
                        count--;
                    }
                }
            }
        }

        #endregion

        #region Nested type: TechInfoItem

        /// <summary>
        /// A TechInfoItem abstracts a <see cref="TechSchool"/> or a <see cref="TechField"/>.
        /// It is a glue class for  DataBinding to the various WPF-Controls (ListControls)
        /// and the underlying DataModel.
        /// </summary>
        /// <remarks>
        /// The <see cref="Highlight"/> propety will be data bound with a <see cref="DataTrigger"/>  
        /// and starts a Flash Animation. Look at ResearchPage.xaml
        /// Mostly, the TechInfoItem will be bound to a DataTemplate 
        /// for the <see cref="ResearchPage.TechSchoolListView"/> or the <see cref="ResearchPage.TechFieldListView"/>
        /// </remarks>
        public class TechInfoItem : INotifyPropertyChanged, IEquatable<TechInfoItem>
        {
            /// <summary>
            /// The short description of the Technology School or the Technology Field
            /// </summary>
            private readonly string name;

            /// <summary>
            /// The Research Percentage Management of the Technology School or Technology Field
            /// </summary>
            private readonly ResearchItem researchItem;

            /// <summary>
            /// The Container List of the items.
            /// </summary>
            private readonly TechInfoCollection techInfoContainer;

            /// <summary>
            /// Initializes a new instance of the <see cref="TechInfoItem"/> class.
            /// </summary>
            /// <param name="infoCollection">The info collection.</param>
            /// <param name="item">The item.</param>
            /// <param name="description">The description.</param>
            /// <param name="highlight">true, if this item should be animated</param>
            public TechInfoItem(TechInfoCollection infoCollection, ResearchItem item, string description, bool highlight)
            {
                techInfoContainer = infoCollection;
                researchItem = item;
                name = description;
                this.Highlight = highlight;
            }

            /// <summary>
            /// Gets a value indicating whether this <see cref="TechInfoItem"/> is highlight.
            /// </summary>
            /// <value><c>true</c> if highlight; otherwise, <c>false</c>.</value>
            public bool Highlight { get; set; }

            /// <summary>
            /// Gets the Research Percentage Management of the Technology School or Technology Field
            /// </summary>
            /// <value>The research item.</value>
            public ResearchItem ResearchItem
            {
                get { return researchItem; }
            }

            /// <summary>
            /// Gets the short name of the Technology School or the Technology Field
            /// </summary>
            /// <remarks>
            /// This property will be databound in the <see cref="ResearchPage" />
            /// </remarks>
            /// <value>The Name of the School or Technology Field.</value>
            public string Name
            {
                get { return name; }
            }

            /// <summary>
            /// Gets or sets the percentage value of the TechSchool or TechFiled.
            /// </summary>
            /// <remarks>
            /// This property will be two-way databound in the <see cref="ResearchPage" />
            /// </remarks>
            /// <value>The percentage.</value>
            public int Percentage
            {
                get { return researchItem.ResearchPercentage; }
                set
                {
                    if (researchItem.ResearchPercentage != value)
                    {
                        PercentageInternal = value;
                        techInfoContainer.OnTechInfoItemChanged(this);
                    }
                }
            }

            /// <summary>
            /// Internal property to modify the Research Percentage values of the TechSchool or Techfield
            /// </summary>
            /// <value>The percentage internal.</value>
            internal int PercentageInternal
            {
                get { return researchItem.ResearchPercentage; }
                set
                {
                    if (value < 0) value = 0;
                    if (value > 100) value = 100;

                    if (researchItem.ResearchPercentage != value)
                    {
                        researchItem.ResearchPercentage = value;
                        if (PropertyChanged != null)
                            PropertyChanged(this, new PropertyChangedEventArgs("Percentage"));
                    }
                }
            }

            #region IEquatable<TechInfoItem> Members

            /// <summary>
            /// Indicates whether the current object is equal to another object of the same type.
            /// </summary>
            /// <param name="other">An object to compare with this object.</param>
            /// <returns>
            /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
            /// </returns>
            public bool Equals(TechInfoItem other)
            {
                if (ReferenceEquals(null, other)) return false;
                if (ReferenceEquals(this, other)) return true;
                return Equals(other.name, name);
            }

            #endregion

            #region INotifyPropertyChanged Members

            /// <summary>
            /// Notify WPF, that the PercentageProperty has changed.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;

            #endregion

            /// <summary>
            /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
            /// </summary>
            /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
            /// <returns>
            /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
            /// </returns>
            /// <exception cref="T:System.NullReferenceException">
            /// The <paramref name="obj"/> parameter is null.
            /// </exception>
            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                if (ReferenceEquals(this, obj)) return true;
                if (obj.GetType() != typeof(TechInfoItem)) return false;
                return Equals((TechInfoItem)obj);
            }

            /// <summary>
            /// Serves as a hash function for a particular type.
            /// </summary>
            /// <returns>
            /// A hash code for the current <see cref="T:System.Object"/>.
            /// </returns>
            public override int GetHashCode()
            {
                return name.GetHashCode();
            }

            /// <summary>
            /// Implements the operator ==.
            /// </summary>
            /// <param name="left">The left.</param>
            /// <param name="right">The right.</param>
            /// <returns>The result of the operator.</returns>
            public static bool operator ==(TechInfoItem left, TechInfoItem right)
            {
                return Equals(left, right);
            }

            /// <summary>
            /// Implements the operator !=.
            /// </summary>
            /// <param name="left">The left.</param>
            /// <param name="right">The right.</param>
            /// <returns>The result of the operator.</returns>
            public static bool operator !=(TechInfoItem left, TechInfoItem right)
            {
                return !Equals(left, right);
            }
        }

        #endregion
    }
}